package IC.LIR;

import java.util.HashMap;
import java.util.Map;

import IC.AST.*;



public class ClassLayout {
	
	//Table to store class Virtual Methods - Dispatch Vector
	private Map<Method,Integer> methodToOffset;
	//Table to translate field to offset in the memory layout
	private Map<Field,Integer> fieldToOffset;
	
	//Name of the class
	private String className;
	
	private int numMethods = 0;
	private int numFields = 0;
	
	//constructor for a class layout
	public ClassLayout(ICClass icClass) {
		
		className = icClass.getName();
		methodToOffset = new HashMap<Method,Integer>();
		fieldToOffset = new HashMap<Field,Integer>();
		//put all the fields in the class layout
		for (Field field : icClass.getFields()) {
			fieldToOffset.put(field, ++numFields);
		}
		//put all the methods in the class layout dispatch vector
		for (Method method : icClass.getMethods()) {
			methodToOffset.put(method, numMethods++);
		}
	}
	
	public ClassLayout(ICClass icClass, ClassLayout superClassLayout) {
		className = icClass.getName();
		methodToOffset = new HashMap<Method,Integer>();
		fieldToOffset = new HashMap<Field,Integer>();
		//Get all the methods and fields of the superclass
		methodToOffset.putAll(superClassLayout.methodToOffset);
		fieldToOffset.putAll(superClassLayout.fieldToOffset);
		numMethods = superClassLayout.getNumMethods();
		numFields = superClassLayout.getNumFields();
		//add new fields
		for (Field field : icClass.getFields()) {
			fieldToOffset.put(field, numFields++);
		}
		//check if we need to override methods
		for (Method thisMethod : icClass.getMethods()) {
			boolean overrides = false;
			for (Method superMethod : methodToOffset.keySet()) {
				if (thisMethod.getName().equals(superMethod.getName())) {
					overrides = true;
					int offset = methodToOffset.get(superMethod);
					methodToOffset.remove(superMethod);
					methodToOffset.put(thisMethod, offset);
					break;
				}
			}
			if (!overrides) {
				methodToOffset.put(thisMethod, numMethods++);
			}
		}
	}
	
	public String getDispatchTable() {
		
		String strDispatchTable = "";
		strDispatchTable += "_DV_" + className + ": [";
		//print methods in dispatch table
		for (int i = 0; i < numMethods; i++) {
			for (Method method : methodToOffset.keySet()) {
				if (method instanceof StaticMethod) {
					continue;
				}
				//check if the offset is consistent
				if (methodToOffset.get(method) == i) {
					strDispatchTable += "_"+className+"_"+method.getName() + ",";
					break;
				}	
			}
		}
		//Delete last comma
		if (strDispatchTable.endsWith(",")) {
			strDispatchTable = strDispatchTable.substring(0, strDispatchTable.length()-1);
		}
		
		strDispatchTable += "]\n#Field Offsets:\n";
		//Print description of fields offset
		for (int i = 0; i <numFields; i++) {
			for (Field field : fieldToOffset.keySet()) {
				if (fieldToOffset.get(field) == i+1) {
					strDispatchTable += "#"+field.getName()+": "+fieldToOffset.get(field)+"\n";
					break;
				}
			}
		}
		
		return strDispatchTable;
	}
	
	// Getters
	
	//get number of methods in the class - Static and virtual
	public int getNumMethods() {
		return numMethods;
	}
	
	//Get number of fields in the class
	public int getNumFields() {
		return numFields;
	}
	
	//Get the name of the class
	public String getClassName() {
		return className;
	}
	
	//Get offset of method
	public int getMethodOffset(Method method) {
		return methodToOffset.get(method);
	}
	
	//Get offset of field
	public int getFieldOffset(Field field) {
		return fieldToOffset.get(field);
	}
	
	//Get a method object from it's name, if non-exist, returns null,
	//otherwise the Method object. 
	//if exist must be unique because no overloading allowed
	public Method getMethodFromName(String methodName) {
		for (Method method: methodToOffset.keySet()) {
			if (method.getName().equals(methodName)) {
				return method;
			}
		}
		return null;
	}
	
	//Get a field object from it's name, if non-exist, returns null.
	//if exist must be on this class layout because every time a classlayout of 
	//an extending class is created, all the fields are copied from it's superclass
	public Field getFieldFromName(String fieldName) {
		for (Field field : fieldToOffset.keySet()) {
			if (field.getName().equals(fieldName)) {
				return field;
			}
		}
		return null;
	}

}
